package ss.pku.jsa.type.inference;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;

import ss.pku.jsa.type.constraint.solver.Native;


public class Location {
	// private boolean classAllocated = false;
	protected int nativeIndex;
	private int hashCode;
	public int locIndex;
	private String cachedFileName = null;
	private long cachedFilePosition = -1L;
	private long cachedLine = -1L;
	private boolean init;

	public Location(String filename, long line, long filepos) {
		this.init = true;
		allocNativeStruct();
		setLine(line);
		setFilePosition(filepos);
		setFileName(filename);
		this.init = false;
		updateHashCode();
		this.locIndex = (JqualPlugin.analysis != null ? ++JqualPlugin.analysis.locCount
				: 0);

		JqualPlugin.analysis.addLocation(this);
	}

	public Location(ASTNode node, CompilationUnit compilationUnit,
			String fileName) {
		this.init = true;
		allocNativeStruct();

		if (fileName == null)
			setFileName("unknown source file");
		else {
			setFileName(fileName);
		}
		if ((node == null) || (compilationUnit == null)) {
			setLine(-1L);
			setFilePosition(-1L);
		} else {
			setFilePosition(node.getStartPosition());
			setLine(compilationUnit.getLineNumber(node.getStartPosition()));
		}
		this.locIndex = (JqualPlugin.analysis != null ? ++JqualPlugin.analysis.locCount
				: 0);
		this.init = false;
		updateHashCode();
		JqualPlugin.analysis.addLocation(this);
	}

	public Location copy() {
		Profile.logMemStart("Location/copy/new Location");
		Location loc = new Location(getFileName(), getLine(), getFilePosition());
		Profile.logMemEnd();
		return loc;
	}

	public static Location getLocationFromNativeIndex(int nativeIndex) {
		Location ret = JqualPlugin.analysis.getLocation(nativeIndex);
		if (ret == null)
			throw new IllegalArgumentException(
					"No Location exists for native index " + nativeIndex);
		return ret;
	}

	public boolean equals(Object o) {
		if (!(o instanceof Location)) {
			return false;
		}
		Location l = (Location) o;
		return (l.getFileName().equals(getFileName()))
				&& (l.getFilePosition() == getFilePosition());
	}

	private void updateHashCode() {
		if (this.init)
			return;
		this.hashCode = (getFileName().hashCode() + new Long(getFilePosition())
				.hashCode());
	}

	public int hashCode() {
		if (this.init)
			throw new IllegalArgumentException(
					"Call to hashcode before Location is initialized. "
							+ toString());
		return this.hashCode;
	}

	public static Location getConservativeAssumptionLocation() {
		Profile.logMemStart("getConservativeAssumptionLocation/new Location");
		Location loc = new Location(
				"(Assumptions made for type without available source code)",
				0L, 0L);
		Profile.logMemEnd();
		return loc;
	}

	private void allocNativeStruct() {
		// this.classAllocated = true;
		this.nativeIndex = Native.allocLocation();
	}

	public long getLine() {
		if (this.cachedLine > -1L)
			return this.cachedLine;

		return Native.getLineFromLocation(this.nativeIndex);
	}

	public long getFilePosition() {
		if (this.cachedFilePosition > -1L)
			return this.cachedFilePosition;

		return Native.getFilePositionFromLocation(this.nativeIndex);
	}

	public String getFileName() {
		if (this.cachedFileName == null) {
			return Native.getFileNameFromLocation(this.nativeIndex);
		}
		return this.cachedFileName;
	}

	public void setLine(long line) {
		Native.setLineForLocation(this.nativeIndex, line);
		this.cachedLine = line;
		updateHashCode();
	}

	public void setFilePosition(long fp) {
		Native.setFilePositionForLocation(this.nativeIndex, fp);
		this.cachedFilePosition = fp;
		updateHashCode();
	}

	public void setFileName(String name) {
		Native.setFileNameForLocation(this.nativeIndex, name);
		this.cachedFileName = name;
		updateHashCode();
	}

	public int getNativeIndex() {
		return this.nativeIndex;
	}

	public static long getLineNumber(ASTNode node,
			CompilationUnit compilationUnit) {
		if ((node == null) || (compilationUnit == null))
			throw new IllegalArgumentException(
					"null argument passed to TypeUtils.getLineNumber");
		long filePosition = node.getStartPosition();
		return compilationUnit.getLineNumber((int) filePosition);
	}

	public static Location getLocation(ASTNode node,
			CompilationUnit compilationUnit, String fileName) {
		return new Location(node, compilationUnit, fileName);
	}

	public String toString() {
		return getFileName() + ": " + getLine() + "(" + getFilePosition() + ")";
	}
}
